﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace Wheels
{
    public static class TreeNavigationUtility
    {
#pragma warning disable 1591

        [Flags]
        internal enum WildcardPos
        {
            None = 0,
            File = 1,
            Dir = 2,
            Both = 3,
        }

        private static readonly char[] smWildcardChars = new char[] { '*', '?' };
        private static readonly char[] smPathSeparators = new char[] { '/', '\\' };

        private static readonly Func<string, string> smGetContainer = path => Path.GetDirectoryName(path);
        private static readonly Func<string, string> smGetItemName = path => Path.GetFileName(path);
        private static readonly Func<string, string, IEnumerable<string>> smGetFiles = (path, pattern) => Directory.GetFiles(path, pattern);
        private static readonly Func<string, string, IEnumerable<string>> smGetDirectories = (path, pattern) => Directory.GetDirectories(path, pattern);

        public static void Navigate(Stack<string> task,
            Func<string, string, IEnumerable<string>> getChildItems,
            Func<string, string, IEnumerable<string>> getChildContainers,
            Func<string, string> getContainer,
            Func<string, string> getItemName,
            Func<string, bool> callback)
        {
            string currentPath;
            while (task.Count > 0)
            {
                currentPath = task.Pop();
                string currentDir = getContainer(currentPath);
                string currentFile = getItemName(currentPath);

                WildcardPos wp = ((currentDir.IndexOfAny(smWildcardChars) < 0) ? WildcardPos.None : WildcardPos.Dir)
                  | ((currentFile.IndexOfAny(smWildcardChars) < 0) ? WildcardPos.None : WildcardPos.File);

                if (wp == WildcardPos.None)
                {
                    if (!callback(currentPath))
                        return;
                }
                else
                {
                    if (wp == WildcardPos.File)
                    {
                        #region
                        try
                        {
                            foreach (string fileItem in getChildItems(currentDir, currentFile).Reverse())
                            {
                                task.Push(fileItem);
                            }
                        }
                        catch (SystemException)
                        {
                        }
                        catch (Exception ex)
                        {
                            System.Diagnostics.Debug.WriteLine(ex.Message + ": " + currentDir + " : " + currentFile);
                        }
                        #endregion
                    }
                    else
                    {
                        #region
                        int iWildcardPos = currentPath.IndexOfAny(smWildcardChars);
                        int iPrefix = currentPath.LastIndexOfAny(smPathSeparators, iWildcardPos) + 1;
                        int iSuffix = currentPath.IndexOfAny(smPathSeparators, iWildcardPos);

                        string prefix = currentPath.Substring(0, iPrefix);
                        string middle = currentPath.Substring(iPrefix, iSuffix - iPrefix);
                        string suffix = currentPath.Substring(iSuffix);

                        string thisDir = null;
                        if ((middle != "*") && middle.All(c => c == '*'))
                        {
                            if (middle != "**")
                                continue;

                            thisDir = prefix + suffix.Substring(1);
                            middle = "*";
                            suffix = suffix[0] + "**" + suffix;
                        }

                        try
                        {
                            foreach (string dirItem in getChildItems(prefix, middle).Reverse())
                            {
                                task.Push(dirItem + suffix);
                            }
                        }
                        catch (SystemException)
                        {
                        }
                        catch (Exception ex)
                        {
                            System.Diagnostics.Debug.WriteLine(ex.Message + ": " + prefix + " : " + middle);
                        }

                        if (thisDir != null)
                            task.Push(thisDir);
                        #endregion
                    }
                }
            }
        }

        public static void Navigate(Stack<string> task,
            Func<string, string, IEnumerable<string>> getChildItems,
            Func<string, string, IEnumerable<string>> getChildContainers,
            Func<string, bool> callback)
        {
            Navigate(task, getChildItems, getChildContainers, smGetContainer, smGetItemName, callback);
        }

        public static Stack<string> Navigate(string task,
            Func<string, string, IEnumerable<string>> getChildItems,
            Func<string, string, IEnumerable<string>> getChildContainers,
            Func<string, string> getContainer,
            Func<string, string> getItemName,
            Func<string, bool> callback)
        {
            Stack<string> stack = new Stack<string>();
            stack.Push(task);
            Navigate(stack, getChildItems, getChildContainers, getContainer, getItemName, callback);
            return stack;
        }

        public static Stack<string> Navigate(string task,
            Func<string, string, IEnumerable<string>> getChildItems,
            Func<string, string, IEnumerable<string>> getChildContainers,
            Func<string, bool> callback)
        {
            return Navigate(task, getChildItems, getChildContainers, smGetContainer, smGetItemName, callback);
        }

        public static IEnumerable<string> Query(string task,
            Func<string, string, IEnumerable<string>> getChildItems,
            Func<string, string, IEnumerable<string>> getChildContainers,
            Func<string, string> getContainer,
            Func<string, string> getItemName)
        {
            Stack<string> stack = new Stack<string>();
            stack.Push(task);

            string item;
            do
            {
                item = null;
                Navigate(stack, getChildItems, getChildContainers, getContainer, getItemName, fn =>
                {
                    item = fn;
                    return false;
                });

                if (item == null)
                    break;

                yield return item;
            } while (true);
        }

        public static IEnumerable<string> Query(string task,
            Func<string, string, IEnumerable<string>> getChildItems,
            Func<string, string, IEnumerable<string>> getChildContainers)
        {
            return Query(task, getChildItems, getChildContainers, smGetContainer, smGetItemName);
        }

        public static void NavigateFileSystem(Stack<string> task, Func<string, bool> callback)
        {
            Navigate(task, smGetFiles, smGetDirectories, callback);
        }

        public static Stack<string> NavigateFileSystem(string task, Func<string, bool> callback)
        {
            return Navigate(task, smGetFiles, smGetDirectories, callback);
        }

        public static IEnumerable<string> QueryFileSystem(string task)
        {
            return Query(task, smGetFiles, smGetDirectories);
        }
    }
}
